
open Chipmunk.Low_level

(* the Segment object *)
module Segment = struct
	type t = cpShape * cpSegmentShape

	let create space startX startY endX endY =
		(* we define the segment object to be static,
		 * so the body will have infinite mass and momentum *)
		let body = cpBodyNew infinity infinity in
		(* and the shape is just a segment *)
		let shape = cpSegmentShapeNew ~body ~a:(cpv startX startY) ~b:(cpv endX endY) ~r:0.0 in
		let () = cpShapeSetElasticity ~shape ~e:1.0 in	
		let () = cpShapeSetFriction ~shape ~u:1.0 in
		(* register shape for collisions in space *)
		let () = cpSpaceAddStaticShape ~space ~shape in
		(* there could be cpShapeSetCollisionType here,
		 * but it is not needed now, defaults to 0 *)
		(shape, cpSegmentShape_of_cpShape shape)

	(* convert shape for drawable representation as Types.Data.t *)
	let show (shape, segment) = 
		let body = cpShapeGetBody ~shape in
		let relative1 = cpSegmentShapeGetA segment in
		let relative2 = cpSegmentShapeGetB segment in
		let absolute1 = cpvadd (cpBodyGetPos ~body)
			(cpvrotate (relative1) (cpBodyGetRot ~body)) in
		let absolute2 = cpvadd (cpBodyGetPos ~body)
			(cpvrotate (relative2) (cpBodyGetRot ~body)) in
		Types.Data.List [
			Types.Data.String "Segment";
			Types.Data.Float absolute1.cp_x;
			Types.Data.Float absolute1.cp_y;
			Types.Data.Float absolute2.cp_x;
			Types.Data.Float absolute2.cp_y]
	
	(* draw segment from the representation created above *)	
	let draw data = match data with
		| Types.Data.List [
			Types.Data.String "Segment";
			Types.Data.Float aX;
			Types.Data.Float aY;
			Types.Data.Float bX;
			Types.Data.Float bY] ->
				(* draw separate lines *)
				let () = GL.glBegin GL.GL_LINES in
					(* set color to white *)
					let () = GL.glColor3 1.0 1.0 1.0 in
					let () = GL.glVertex2 aX aY in
					let () = GL.glVertex2 bX bY in
				let () = GL.glEnd() in
				()
		| _ -> assert false
	
	(* segment doesn't do anything *)	 	
	let step shape = ()
end

module Box = struct
	type t = cpBody * float (* body, size *)

	let genVerts centerX centerY size rot = 
		Array.map 
			(fun v -> cpvadd (cpv centerX centerY) (cpvrotate v rot))
			[|cpv ~x:size ~y:size; 
			  cpv ~x:(size) ~y:(-.size);
			  cpv ~x:(-.size) ~y:(-.size); 
			  cpv ~x:(-.size) ~y:(size) |]
				
	let create space centerX centerY size mass =
        let size' = size *. Config.cellSize *. 0.5 in
		let verts = genVerts 0.0 0.0 size' (cpv ~x:1.0 ~y:0.0) in
		(* calculate the momentum for the given polygon from mass *)
		let moment = cpMomentForPoly mass verts (cpvzero()) in
		(* create body with appriopriate mass and momentum *)
		let body = cpBodyNew mass moment in
		(* starting position *)
		let () = cpBodySetPos body (cpv centerX centerY) in
		let () = cpSpaceAddBody space body in
		(* shape for collision detection, must be convex *)
		let shape = cpPolyShapeNew body verts (cpvzero()) in
		let () = cpShapeSetElasticity shape 0.0 in
		let () = cpShapeSetFriction shape 1.5 in
		(* explicite set collision type (the default is 0 too) *)
		let () = cpShapeSetCollisionType shape 0 in
		(* register the shape in space *)
		let () = cpSpaceAddShape space shape in
		(* our object representation *)
		(body, size)
	
	let show (body, size) = 
		let rot = cpBodyGetRot ~body in
		Types.Data.List[
			Types.Data.String "Box";
			Types.Data.Float (cpBodyGetPos ~body).cp_x;
			Types.Data.Float (cpBodyGetPos ~body).cp_y;
			Types.Data.Float size;
			Types.Data.Float rot.cp_x;
			Types.Data.Float rot.cp_y]

	let draw data = match data with
		| Types.Data.List [
			Types.Data.String "Box";
			Types.Data.Float cX;
			Types.Data.Float cY;
			Types.Data.Float size;
			Types.Data.Float rotX;
			Types.Data.Float rotY] ->
				let size = size *. Config.cellSize *. 0.5 in
				let verts = genVerts cX cY size (cpv ~x:rotX ~y:rotY) in
				let () = GL.glBegin GL.GL_LINE_LOOP in
				let () = GL.glColor3 1.0 1.0 1.0 in
				let () = 
					for ii = 0 to pred 4 do
						GL.glVertex2 verts.(ii).cp_x verts.(ii).cp_y
					done 
				in GL.glEnd()
		| _ -> assert false

	let step (body, size) = ()
end

module Trigger = struct
   (* trigger is defined by its shape and type *)
   (* type is an int, and it equals trigger's CollisionType *)
   type t = cpShape * int

   (* == defined trigger types == *)
   let maxType  = 104
   (* debug trigger, changes player's color *)
   let tDebug   = 100
   (* jumper trigger, appies an upward force *)
   let tJumper  = 101
   (* same as tDebug, but is solid -- creates actual collision *)
   let tSolid   = 102
   (* triggers to add/remove HP *)
   let tDamage  = 103
   let tHeal    = 104
   let typeFromString str = match str with
      | "debug"   -> tDebug
      | "jumper"  -> tJumper
      | "solid"   -> tSolid
      | "damage"  -> tDamage
      | "heal"    -> tHeal
      | _ -> raise (Types.Data.BadParse ("unknown trigger type `" ^ str ^ "'\013"))
   let typeToString num = match num with
      | 100 -> "debug"
      | 101 -> "jumper"
      | 102 -> "solid"
      | 103 -> "damage"
      | 104 -> "heal"
      | _ -> assert false

   (* each trigger type has a set of collision functions:
      (begin, preSolve, postSolve, separate) *)
   let trNothing ret player = ret
   let getFunctions trType = match trType with
      | 100 -> (Player.makeCyan true, Player.makeBlue false, Player.makeCyan (), Player.makeGreen ()) 
      | 101 -> (trNothing true, Player.jump false, trNothing (), trNothing ())
      | 102 -> (Player.makeBlue true, Player.makeCyan true, Player.makeBlue (), Player.makeGreen ()) 
      | 103 -> (Player.triggerDamage 30.0 false, trNothing false, trNothing (), trNothing ())
      | 104 -> (Player.triggerDamage (-.30.0) false, trNothing false, trNothing (), trNothing ())
      | _ -> assert false

   (* generate vertices array from sides' coordinates *)
   let genVerts lbX lbY width height =
      Array.map (fun v -> cpvadd (cpv lbX lbY) v)
      [| cpvzero(); cpv 0.0 height; cpv width height; cpv width 0.0 |]

	let create space strType lbX lbY width height =
      (* convert type to number *)
      let trType = typeFromString strType in
      (* get vertices *)
      let verts = genVerts lbX lbY width height in
      (* static body *)
      let body = cpBodyNew infinity infinity in
      (* trigger's rectangle *)
      let shape = cpPolyShapeNew body verts (cpvzero()) in
      (* set sensor property so no actual collisions happen *)
      (* let () = cpShapeSetSensor ~shape true in *)
      (* set collision type for trigger type *)
      let () = cpShapeSetCollisionType shape trType in
      (* register object *)
      let () = cpSpaceAddStaticShape space shape in
      (* object representation *)
      (shape, trType)

   let show (shape, trType) =
      (* get left bottom corner and dimensions *)
      let verts = cpPolyShapeGetVects (cpPolyShape_of_cpShape shape) in
      let lbv = verts.(0) in
      let dim = cpvsub (verts.(2)) lbv in
      Types.Data.List[
         Types.Data.String "Trigger";
         Types.Data.String (typeToString trType);
         Types.Data.Float lbv.cp_x;
         Types.Data.Float lbv.cp_y;
         Types.Data.Float dim.cp_x;
         Types.Data.Float dim.cp_y]

   (* draw semitransparent rectangle *)
   let draw data = match data with
      | Types.Data.List [
         Types.Data.String "Trigger";
         Types.Data.String strType;
         Types.Data.Float lbX;
         Types.Data.Float lbY;
         Types.Data.Float width;
         Types.Data.Float height] ->
            let verts = genVerts lbX lbY width height in
            let () = GL.glBegin GL.GL_POLYGON in
            let () = GL.glPolygonMode GL.GL_FRONT_AND_BACK GL.GL_FILL in
            let () = GL.glColor4 1.0 1.0 1.0 0.1 in
            let () =
               for ii = 0 to 3 do
                  GL.glVertex2 verts.(ii).cp_x verts.(ii).cp_y
               done
            in GL.glEnd()
      | _ -> assert false

   (* triggers are static *)
   let step (shape, trType) = ()
end

module Bullet = struct
   (* bullets are [for now]: points on display, small circles to the engine *)
   (* bullet is defined by its body and type *)
   (* body's data contains ID of the player who shot the bullet and bullet type *)
   type t = cpBody * int

   (* defined bullet types *)
   let collisionType = 13
   let maxType = 0
   let tSimple = 0
   let typeFromString str = match str with
      | "simple" -> tSimple
      | _ -> raise (Types.Data.BadParse ("unknown bullet type `" ^ str ^ "'\013"))
   let typeToString bType = match bType with
      | 0 -> "simple"
      | _ -> assert false

   let codeData bType shooterID =
      if shooterID<0 then -(bType+1)
      else (shooterID lsl 8)+bType
   let decodeData num =
      if num<0 then ((-num)-1,-1)
      else (num land 255, num lsr 8)

   (* physical properties *)
   let size = Array.map (fun x -> x *. Config.cellSize)
      (* radius in cells: *)
      [| 0.2 |]
   let elast = [| 0.7 |]
   let frict = [| 0.3 |]
   let mass = [| 20.0 |]
   (* forces applied on fire *)
   let startF = [| 100.0 |]
   (* functions for collisions with players *)
   let onPlayer = [| Player.damage 20.0 |]

   (* general function for collision handler *)
   (* [owner] hit [owned] with bullet type [bType] *)
   let hit bType owned owner =
      (onPlayer.(bType)) owned owner

   let create space strType spawnX spawnY direction shooterID = 
      let bType = typeFromString strType in
      let size' = size.(bType) in
      (* body with appropariate values *)
		let moment = cpMomentForCircle mass.(bType) 0.0 size' (cpvzero()) in
		let body = cpBodyNew mass.(bType) moment in
		let () = cpBodySetPos body (cpv spawnX spawnY) in
      let () = cpBodySetData body (codeData bType shooterID) in
		(* shape for collision detection + bullet type's properties *)
		let shape = cpCircleShapeNew body size' (cpvzero()) in
		let () = cpShapeSetElasticity shape elast.(bType) in
		let () = cpShapeSetFriction shape frict.(bType) in
		let () = cpShapeSetCollisionType shape collisionType in
      (* register objects *)
		let () = cpSpaceAddBody space body in
		let () = cpSpaceAddShape space shape in
      (* ``imma firin' mah lazor!'' *) (* na razie tylko poziomo *)
      let () = cpBodyApplyImpulse body (cpv (direction *. startF.(bType)) 0.0) (cpvzero()) in
		(* our object representation *)
		(body, bType)

   let show (body,bType) = 
      Types.Data.List [
         Types.Data.String "Bullet";
         Types.Data.Float (cpBodyGetPos body).cp_x;
         Types.Data.Float (cpBodyGetPos body).cp_y;
         Types.Data.Int   bType]

   let draw data = match data with
      | Types.Data.List [
         Types.Data.String "Bullet";
         Types.Data.Float posX;
         Types.Data.Float posY;
         Types.Data.Int   bType] ->
            let r = size.(bType) in
            GL.glBegin GL.GL_LINES;
            GL.glColor3 1.0 0.0 0.0;
            GL.glVertex2 (posX-.r) posY;
            GL.glVertex2 (posX+.r) posY;
            GL.glVertex2 posX (posY-.r);
            GL.glVertex2 posX (posY+.r);
            GL.glEnd()
      | _ -> assert false

   (* nothing special for now *)
   let step (body,bType) = ()
end

type t = Segment of Segment.t
	   | Box of Box.t
      | Trigger of Trigger.t
      | Bullet of Bullet.t

let show obj = match obj with
	| Segment segment -> Segment.show segment
	| Box box -> Box.show box
   | Trigger trig -> Trigger.show trig
   | Bullet bul -> Bullet.show bul

let draw data = match data with
	| Types.Data.List (Types.Data.String "Segment" :: _) -> 
		Segment.draw data
	| Types.Data.List (Types.Data.String "Box" :: _) -> 
		Box.draw data
   | Types.Data.List (Types.Data.String "Trigger" :: _) ->
      Trigger.draw data
   | Types.Data.List (Types.Data.String "Bullet" :: _) ->
      Bullet.draw data
	| data -> failwith ("Don't know how to draw: " ^ Types.Data.show data)

let step obj = match obj with
	| Segment segment -> Segment.step segment
	| Box box -> Box.step box
   | Trigger trig -> Trigger.step trig
   | Bullet bul -> Bullet.step bul
